{
 "cells": [
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": [
    "# 构建一个聊天机器人\n",
    "# 概述\n",
    "我们将通过一个示例来设计和实现一个基于大型语音模型的聊天机器人。这个聊天机器人将能够进行对话并记住之前的互动\n",
    "请注意，我们构建的这个聊天机器人将仅使用语言模型进行对话。您可能还在寻找其他几个相关概念：\n",
    "- [对话式RAG](https://www.langchain.com.cn/docs/tutorials/qa_chat_history/): 在外部数据源上启用聊天机器人体验\n",
    "- [代理](https://www.langchain.com.cn/docs/tutorials/agents/): 构建一个可以采取行动的聊天机器人\n",
    "\n",
    "# 安装LangChain\n",
    "- `pip install langchain`\n",
    "- `conda install langchain -c conda-forge`\n",
    "\n",
    "# 实例化一个大语言模型，这边使用deepseek"
   ],
   "id": "9f1b12ed4ece39f7"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-09-18T01:42:57.931590Z",
     "start_time": "2025-09-18T01:42:57.914750Z"
    }
   },
   "cell_type": "code",
   "source": [
    "from langchain_core.messages import HumanMessage, SystemMessage\n",
    "from langchain_community.chat_models import ChatOpenAI\n",
    "\n",
    "# 使用 DeepSeek 的兼容 OpenAI 接口\n",
    "model = ChatOpenAI(\n",
    "    model=\"deepseek-chat\",\n",
    "    base_url=\"https://api.deepseek.com/v1\",  # DeepSeek 的 OpenAI 兼容接口\n",
    "    api_key=\"sk-a76a85b93093439ba3dc5b6dedfc51e5\",\n",
    "    temperature=0,\n",
    "    max_tokens=2048,\n",
    ")"
   ],
   "id": "928b668f2586f1d0",
   "outputs": [],
   "execution_count": 5
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "让我们首先直接使用模型。ChatModel是LangChain“运行接口”的实例，这意味着它们提供了一个标准接口供我们与之交互。要简单地调用模型，我们可以将消息列表传递给.invoke方法。",
   "id": "7dd03af0956000b6"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-09-18T01:43:03.187616Z",
     "start_time": "2025-09-18T01:42:57.944279Z"
    }
   },
   "cell_type": "code",
   "source": [
    "#<!--IMPORTS:[{\"imported\": \"HumanMessage\", \"source\": \"langchain_core.messages\", \"docs\": \"https://python.langchain.com/api_reference/core/messages/langchain_core.messages.human.HumanMessage.html\", \"title\": \"Build a Chatbot\"}]-->\n",
    "\n",
    "from langchain_core.messages import HumanMessage\n",
    "\n",
    "model.invoke([HumanMessage(content=\"你好, 我是小明\")])"
   ],
   "id": "24de071e11404d8d",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "AIMessage(content='你好小明！很高兴认识你。我是DeepSeek-V3，你的智能助手。有什么可以帮你的吗？无论是学习、工作还是生活中的问题，我都会尽力为你解答！😊', additional_kwargs={}, response_metadata={'token_usage': {'completion_tokens': 39, 'prompt_tokens': 9, 'total_tokens': 48, 'completion_tokens_details': None, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': 0}, 'prompt_cache_hit_tokens': 0, 'prompt_cache_miss_tokens': 9}, 'model_name': 'deepseek-chat', 'system_fingerprint': 'fp_08f168e49b_prod0820_fp8_kvcache', 'finish_reason': 'stop', 'logprobs': None}, id='run--2e9921cc-6878-41f8-bef3-7c3d4021bb5b-0')"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 6
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "模型本身没有任何状态概念。例如，如果您问一个后续问题",
   "id": "14474563df62d471"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-09-18T01:43:08.047803Z",
     "start_time": "2025-09-18T01:43:03.207425Z"
    }
   },
   "cell_type": "code",
   "source": "model.invoke([HumanMessage(content=\"我是谁？\")])",
   "id": "2b4b8d1b725097dd",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "AIMessage(content='你是使用 DeepSeek-V3 的用户！😊 但如果你希望我更具体地回答，可以告诉我一些关于你的信息，例如你的名字、兴趣或任何你愿意分享的内容，我会努力为你提供更贴心的帮助！', additional_kwargs={}, response_metadata={'token_usage': {'completion_tokens': 48, 'prompt_tokens': 7, 'total_tokens': 55, 'completion_tokens_details': None, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': 0}, 'prompt_cache_hit_tokens': 0, 'prompt_cache_miss_tokens': 7}, 'model_name': 'deepseek-chat', 'system_fingerprint': 'fp_08f168e49b_prod0820_fp8_kvcache', 'finish_reason': 'stop', 'logprobs': None}, id='run--0da1c53f-36c0-4def-af01-e8bc826b0f58-0')"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 7
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": [
    "我们可以看到，它没有将之前的对话轮次作为上下文，因此无法回答问题。这会导致糟糕的聊天机器人体验！\n",
    "为了绕过这个问题，我们需要将整个对话历史传递给模型。让我们看看这样会发生什么："
   ],
   "id": "c848cda1df1de256"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-09-18T01:43:12.466377Z",
     "start_time": "2025-09-18T01:43:08.059771Z"
    }
   },
   "cell_type": "code",
   "source": [
    "# <!--IMPORTS:[{\"imported\": \"AIMessage\", \"source\": \"langchain_core.messages\", \"docs\": \"https://python.langchain.com/api_reference/core/messages/langchain_core.messages.ai.AIMessage.html\", \"title\": \"Build a Chatbot\"}]-->\n",
    "\n",
    "from langchain_core.messages import AIMessage\n",
    "\n",
    "model.invoke([\n",
    "    HumanMessage(content=\"你好, 我是小明\"),\n",
    "    AIMessage(content=\"你好, 小明\"),\n",
    "    HumanMessage(content=\"我是谁？\")\n",
    "])"
   ],
   "id": "57b238b299cea48",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "AIMessage(content='你是小明呀！刚刚你已经自我介绍过了哦～如果这是个脑筋急转弯或者有特别的意思，我可能还没猜到呢！😄', additional_kwargs={}, response_metadata={'token_usage': {'completion_tokens': 28, 'prompt_tokens': 20, 'total_tokens': 48, 'completion_tokens_details': None, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': 0}, 'prompt_cache_hit_tokens': 0, 'prompt_cache_miss_tokens': 20}, 'model_name': 'deepseek-chat', 'system_fingerprint': 'fp_08f168e49b_prod0820_fp8_kvcache', 'finish_reason': 'stop', 'logprobs': None}, id='run--3cd2f514-4e74-4317-a1b3-639ff34cb3fe-0')"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 8
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": [
    "现在我们可以看到我们得到了一个好的回应！\n",
    "\n",
    "这是支撑聊天机器人进行对话交互的基本理念。那么我们如何最好的实现这一点呢？"
   ],
   "id": "d290739e010abf35"
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": [
    "# 消息历史\n",
    "\n",
    "我们可以使用消息历史类，来包装我们的模型，使其具有状态。这将跟踪模型的输入和输出，并将其存储在某个数据存储中。未来的交互将加载这些消息，并将其作为输入的一部分传递给链。让我们看看如何使用这个！\n",
    "首先，让我们确保安装 `langchain-community`，因为我们将使用其中的集成来存储消息历史。\n",
    "\n",
    "```bash\n",
    "pip install langchain-community -i https://pypi.tuna.tsinghua.edu.cn/simple/\n",
    "```"
   ],
   "id": "355f56d069a0695a"
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "之后，我们可以导入相关类，并设置我们的链，该链包装模型并添加此消息历史。这里的一个关键部分是我们作为 `get_session_history` 传入的函数。这个函数预计接受一个 `session_id` 并返回一个消息历史对象。这个`session_id`用户区分不同的对话，并作为配置的有一部分再调用新链时传入(我们将展示如何做到这一点)",
   "id": "509c33b9681e80f7"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-09-18T01:43:12.538526Z",
     "start_time": "2025-09-18T01:43:12.477240Z"
    }
   },
   "cell_type": "code",
   "source": [
    "# <!--IMPORTS:[{\"imported\": \"BaseChatMessageHistory\", \"source\": \"langchain_core.chat_history\", \"docs\": \"https://python.langchain.com/api_reference/core/chat_history/langchain_core.chat_history.BaseChatMessageHistory.html\", \"title\": \"Build a Chatbot\"}, {\"imported\": \"InMemoryChatMessageHistory\", \"source\": \"langchain_core.chat_history\", \"docs\": \"https://python.langchain.com/api_reference/core/chat_history/langchain_core.chat_history.InMemoryChatMessageHistory.html\", \"title\": \"Build a Chatbot\"}, {\"imported\": \"RunnableWithMessageHistory\", \"source\": \"langchain_core.runnables.history\", \"docs\": \"https://python.langchain.com/api_reference/core/runnables/langchain_core.runnables.history.RunnableWithMessageHistory.html\", \"title\": \"Build a Chatbot\"}]-->\n",
    "\n",
    "from langchain_core.chat_history import (\n",
    "    BaseChatMessageHistory,\n",
    "    InMemoryChatMessageHistory,\n",
    ")\n",
    "from langchain_core.runnables.history import RunnableWithMessageHistory\n",
    "\n",
    "store = {}\n",
    "\n",
    "def get_session_history(session_id: str) -> BaseChatMessageHistory:\n",
    "    if session_id not in store:\n",
    "        store[session_id] = InMemoryChatMessageHistory() # 创建一个消息历史对象，存在内存中\n",
    "    return store[session_id]\n",
    "\n",
    "with_message_history = RunnableWithMessageHistory(model, get_session_history)"
   ],
   "id": "8f5e1f4ca1eb196b",
   "outputs": [],
   "execution_count": 9
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "我们现在需要创建一个`config`，每次都传递给可运行的部分。这个配置包含的信息不是直接作为输入的一部分，但仍然是有用的。在这种情况下，我们想要包含一个`session_id`。这应该看起来像这样：",
   "id": "9438b5042f22c7e5"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-09-18T01:43:12.545125Z",
     "start_time": "2025-09-18T01:43:12.541966Z"
    }
   },
   "cell_type": "code",
   "source": "config = {\"configurable\": {\"session_id\": \"abc2\"}}",
   "id": "b27fe6636ebfde40",
   "outputs": [],
   "execution_count": 10
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-09-18T01:43:16.995430Z",
     "start_time": "2025-09-18T01:43:12.550998Z"
    }
   },
   "cell_type": "code",
   "source": [
    "response = with_message_history.invoke(\n",
    "    [HumanMessage(content=\"你好, 我是小明\")],\n",
    "    config=config,\n",
    ")\n",
    "\n",
    "response.content\n"
   ],
   "id": "d4d48519023f3d15",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'你好小明！很高兴认识你。我是DeepSeek-V3，可以帮你解答各种问题或陪你聊天。有什么我可以帮你的吗？😊'"
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 11
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-09-18T01:43:22.474301Z",
     "start_time": "2025-09-18T01:43:17.008907Z"
    }
   },
   "cell_type": "code",
   "source": [
    "response = with_message_history.invoke(\n",
    "    [HumanMessage(content=\"我是谁？\")],\n",
    "    config=config,\n",
    ")\n",
    "\n",
    "response.content"
   ],
   "id": "2f43ebc48f612944",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'你是小明呀！刚刚你自我介绍过啦~ 如果这是需要我记住的设定，可以随时告诉我更多信息哦！不过作为AI，每次对话都是新的开始，但我会努力记住当前对话中的上下文细节 😊  \\n有什么想聊的或需要帮助的吗？'"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 12
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "太好了！我们的聊天机器人现在记住了关于我们的事情。如果我们更改配置以引用不同的 `session_id`，我们可以看到它开始新的对话",
   "id": "676327cb42ac9144"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-09-18T01:43:27.406286Z",
     "start_time": "2025-09-18T01:43:22.488267Z"
    }
   },
   "cell_type": "code",
   "source": [
    "# 新的session_id, 就表示一个新的会话\n",
    "config = {\"configurable\": {\"session_id\": \"abc3\"}}\n",
    "response = with_message_history.invoke(\n",
    "    [HumanMessage(content=\"我的名字叫什么？\")],\n",
    "    config=config\n",
    ")\n",
    "\n",
    "response.content"
   ],
   "id": "e91cca269ae0b863",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'你的名字是用户自己设定的哦！如果你曾告诉过我，我可能会记得，但目前我无法直接获取你的名字。不过，你可以直接告诉我你的名字，我会记住并在以后的对话中使用哦！😊'"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 13
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "然而，我们始终可以回到原始对话（因为我们将其保存到数据库中）",
   "id": "a07bdeb08f334d4"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-09-18T01:43:32.050231Z",
     "start_time": "2025-09-18T01:43:27.418837Z"
    }
   },
   "cell_type": "code",
   "source": [
    "# 回到 session_id=\"abc2\" 的对话\n",
    "config = {\"configurable\": {\"session_id\": \"abc2\"}}\n",
    "response = with_message_history.invoke(\n",
    "    [HumanMessage(content=\"我的名字叫什么？\")],\n",
    "    config=config\n",
    ")\n",
    "\n",
    "response.content"
   ],
   "id": "96cc26044ef9bfa1",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'你是小明！这是你刚刚告诉我的名字哦～ 😊  \\n如果这是你喜欢的称呼，我会一直这样叫你！有什么需要帮忙的吗，小明？'"
      ]
     },
     "execution_count": 14,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 14
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": [
    "这就是我们如何支持聊天机器人与多个用户进行对话的方式！\n",
    "\n",
    "现在，我们所做的只是为模型添加了一个简单的持久化层。我们可以通过添加提示词模板来是其变的更近复杂和个性化"
   ],
   "id": "d069e350854f3519"
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": [
    "# 提示词模板\n",
    "\n",
    "提示词模板帮助将原始用户信息转换为大语言模型可以处理的格式。在这种情况下，原始用户输入只是一个消息，我们将其传递给大型语言模型。现在让我们变得更复杂一些。首先，让我们添加一个带有一些自定义指令的系统消息（但仍然将消息作为输入）。接下来，我们将添加除了消息之外的更多输入。\n",
    "\n",
    "首先，让我们添加一个系统消息。为此，我们将创建一个`ChatPromptTemplate`。我们将利用`MessagesPlaceholder`来传递所有消息."
   ],
   "id": "283fe5cc36118b6a"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-09-18T01:43:32.203936Z",
     "start_time": "2025-09-18T01:43:32.069853Z"
    }
   },
   "cell_type": "code",
   "source": [
    "# <!--IMPORTS:[{\"imported\": \"ChatPromptTemplate\", \"source\": \"langchain_core.prompts\", \"docs\": \"https://python.langchain.com/api_reference/core/prompts/langchain_core.prompts.chat.ChatPromptTemplate.html\", \"title\": \"Build a Chatbot\"}, {\"imported\": \"MessagesPlaceholder\", \"source\": \"langchain_core.prompts\", \"docs\": \"https://python.langchain.com/api_reference/core/prompts/langchain_core.prompts.chat.MessagesPlaceholder.html\", \"title\": \"Build a Chatbot\"}]-->\n",
    "\n",
    "from langchain_core.prompts import ChatPromptTemplate,MessagesPlaceholder\n",
    "\n",
    "prompt = ChatPromptTemplate.from_messages(\n",
    "    [\n",
    "        (\n",
    "            \"system\",\n",
    "            \"你是一个得力的助手，用你的最好的能力回答用户问题。\"\n",
    "        ),\n",
    "        MessagesPlaceholder(variable_name=\"messages\"),\n",
    "    ]\n",
    ")\n",
    "\n",
    "chain = prompt | model"
   ],
   "id": "1faa3e8f2ae0ee50",
   "outputs": [],
   "execution_count": 15
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "请注意，这稍微改变了输入类型，我们现在传递的是一个包含`messages`键的字典，其中包含了一系列消息，而不是传递消息列表。",
   "id": "9a94e1a4b09e4310"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-09-18T01:43:35.844960Z",
     "start_time": "2025-09-18T01:43:32.209794Z"
    }
   },
   "cell_type": "code",
   "source": [
    "response = chain.invoke({\"messages\": [HumanMessage(content=\"你好, 我是小明\")]})\n",
    "\n",
    "print(response.content)\n",
    "\n",
    "print('-' * 100)\n",
    "print(response)\n"
   ],
   "id": "cf596ac1d0a869a4",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "你好小明！很高兴认识你。有什么我可以帮助你的吗？\n",
      "----------------------------------------------------------------------------------------------------\n",
      "content='你好小明！很高兴认识你。有什么我可以帮助你的吗？' additional_kwargs={} response_metadata={'token_usage': {'completion_tokens': 13, 'prompt_tokens': 24, 'total_tokens': 37, 'completion_tokens_details': None, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': 0}, 'prompt_cache_hit_tokens': 0, 'prompt_cache_miss_tokens': 24}, 'model_name': 'deepseek-chat', 'system_fingerprint': 'fp_08f168e49b_prod0820_fp8_kvcache', 'finish_reason': 'stop', 'logprobs': None} id='run--252d9070-01e6-44c8-a612-02bd5740ecfd-0'\n"
     ]
    }
   ],
   "execution_count": 16
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "我们现在可以将其包装在与之前相同的消息历史对象中",
   "id": "a321a0e540f2588b"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-09-18T01:43:39.148692Z",
     "start_time": "2025-09-18T01:43:35.863288Z"
    }
   },
   "cell_type": "code",
   "source": [
    "with_message_history = RunnableWithMessageHistory(chain, get_session_history)\n",
    "\n",
    "config = {\"configurable\": {\"session_id\": \"abc5\"}}\n",
    "\n",
    "response = with_message_history.invoke(\n",
    "    [HumanMessage(content=\"你好, 我是小明\")],\n",
    "    config=config,\n",
    ")\n",
    "\n",
    "response.content"
   ],
   "id": "d7ba0ee9c007cff3",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'你好小明！很高兴认识你。有什么我可以帮助你的吗？'"
      ]
     },
     "execution_count": 17,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 17
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-09-18T01:43:42.758506Z",
     "start_time": "2025-09-18T01:43:39.161036Z"
    }
   },
   "cell_type": "code",
   "source": [
    "response = with_message_history.invoke(\n",
    "    [HumanMessage(content=\"我的名字叫什么？\")],\n",
    "    config=config,\n",
    ")\n",
    "\n",
    "response.content"
   ],
   "id": "9edfdc22689d285",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'你的名字是小明！刚才你提到过哦。有什么其他问题需要我帮忙吗？'"
      ]
     },
     "execution_count": 18,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 18
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "太棒了，现在让我们的提示词变得更复杂一点。假设提示模板现在看起来像这样：",
   "id": "a44b840a97c944b6"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-09-18T01:43:42.775233Z",
     "start_time": "2025-09-18T01:43:42.772107Z"
    }
   },
   "cell_type": "code",
   "source": [
    "prompt = ChatPromptTemplate.from_messages(\n",
    "    [\n",
    "        (\n",
    "            \"system\",\n",
    "            \"你是一个得力的助手，请使用{language}语言，用你出色的能力回答用户的问题\"\n",
    "        ),\n",
    "        MessagesPlaceholder(variable_name=\"messages\"),\n",
    "    ]\n",
    ")\n",
    "\n",
    "chain = prompt | model"
   ],
   "id": "e50837604d2be81b",
   "outputs": [],
   "execution_count": 19
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "请注意，我们在提示中添加了一个新的 `language`输入。我们现在可以调用链并传入我们选择的语言。",
   "id": "446b5ec77ad658fb"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-09-18T01:43:47.148416Z",
     "start_time": "2025-09-18T01:43:42.780115Z"
    }
   },
   "cell_type": "code",
   "source": [
    "response = chain.invoke(\n",
    "    {\"messages\": [HumanMessage(content=\"你好, 我是小明\")], \"language\":\"英文\"}\n",
    ")\n",
    "response.content"
   ],
   "id": "c3b129611eef0fac",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'Hello Xiaoming! Nice to meet you. How can I assist you today?'"
      ]
     },
     "execution_count": 20,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 20
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "现在，让我们将这个更复杂的链封装在一个消息历史中。这次，由于输入中有多个建，我们需要指定正确的键来保存聊天历史",
   "id": "34f1d9d685024961"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-09-18T01:43:50.094037Z",
     "start_time": "2025-09-18T01:43:47.161966Z"
    }
   },
   "cell_type": "code",
   "source": [
    "with_message_history = RunnableWithMessageHistory(\n",
    "    chain,\n",
    "    get_session_history,\n",
    "    input_messages_key=\"messages\"\n",
    ")\n",
    "\n",
    "config = {\"configurable\": {\"session_id\": \"abc11\"}}\n",
    "response = with_message_history.invoke(\n",
    "    {\"messages\": [HumanMessage(content=\"你好，我是小明\")], \"language\": \"英文\"},\n",
    "    config=config,\n",
    ")\n",
    "\n",
    "response.content"
   ],
   "id": "4fc8cea40fc650c4",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'Hello Xiaoming! Nice to meet you. How can I assist you today?'"
      ]
     },
     "execution_count": 21,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 21
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-09-18T01:43:53.607982Z",
     "start_time": "2025-09-18T01:43:50.110117Z"
    }
   },
   "cell_type": "code",
   "source": [
    "response = with_message_history.invoke(\n",
    "    {\"messages\": [HumanMessage(content=\"我的名字叫什么？\")], \"language\": \"英文\"},\n",
    "    config=config,\n",
    ")\n",
    "\n",
    "response.content"
   ],
   "id": "1c000e6c9213c321",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "\"Your name is Xiaoming, as you introduced yourself earlier. Is there anything specific you'd like help with?\""
      ]
     },
     "execution_count": 22,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 22
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": [
    "# 管理对话历史\n",
    "\n",
    "构建聊天机器人时，一个重要的概念是如何管理对话历史。如果不加以管理，消息列表将无限增长，并可能溢出大型语言的上下文窗口。因此，添加一个限制您传入的消息大小的步骤是很重要的。\n",
    "\n",
    "**重要的是，您需要在提示模板之前，但在从消息历史加载之前的消息之后执行此操作**\n",
    "\n",
    "我们可以通过在提示前添加一个简单的步骤，适当地修改 `messages` 键，然后将该新链封装在消息历史类中来实现。\n",
    "\n",
    "LangChain 提供了一些内置的助手来 [管理消息列表](https://www.langchain.com.cn/docs/how_to/#messages)。在这种情况下，我们将使用 [trim_messages](https://www.langchain.com.cn/docs/how_to/trim_messages/) 助手来减少我们发送给模型的消息数量。修剪器允许我们指定希望保留的令牌数量，以及其他参数，例如是否希望始终保留系统消息以及是否允许部分消息"
   ],
   "id": "5df972a48f4d072e"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-09-18T01:43:53.629667Z",
     "start_time": "2025-09-18T01:43:53.622188Z"
    }
   },
   "cell_type": "code",
   "source": [
    "# <!--IMPORTS:[{\"imported\": \"SystemMessage\", \"source\": \"langchain_core.messages\", \"docs\": \"https://python.langchain.com/api_reference/core/messages/langchain_core.messages.system.SystemMessage.html\", \"title\": \"Build a Chatbot\"}, {\"imported\": \"trim_messages\", \"source\": \"langchain_core.messages\", \"docs\": \"https://python.langchain.com/api_reference/core/messages/langchain_core.messages.utils.trim_messages.html\", \"title\": \"Build a Chatbot\"}]-->\n",
    "\n",
    "from langchain_core.messages import SystemMessage, trim_messages\n",
    "\n",
    "\n",
    "\n",
    "# 简单的 token 计数器（按空格分割，近似 token 数量）\n",
    "def simple_token_counter(messages):\n",
    "    total_tokens = 0\n",
    "    for message in messages:\n",
    "        # 近似计算：按空格分割单词\n",
    "        tokens = len(message.content.split())\n",
    "        total_tokens += tokens\n",
    "    return total_tokens\n",
    "\n",
    "trimmer = trim_messages(\n",
    "    max_tokens=10,\n",
    "    strategy=\"last\",\n",
    "    token_counter=simple_token_counter,\n",
    "    include_system=True,\n",
    "    allow_partial=False,\n",
    "    start_on=\"human\",\n",
    ")\n",
    "\n",
    "messages = [\n",
    "    SystemMessage(content=\"你是一个得力的助手，请使用{language}语言，用你出色的能力回答用户问题\"),\n",
    "    HumanMessage(content=\"你好, 我是小明\"),\n",
    "    AIMessage(content=\"你好\"),\n",
    "    HumanMessage(content=\"我喜欢吃冰淇淋\"),\n",
    "    AIMessage(content=\"非常好\"),\n",
    "    HumanMessage(content=\"2+2等于多少\"),\n",
    "    AIMessage(content=\"4\"),\n",
    "    HumanMessage(content=\"谢谢\"),\n",
    "    AIMessage(content=\"没问题!\"),\n",
    "    HumanMessage(content=\"玩的开心吗?\"),\n",
    "    AIMessage(content=\"好的!\"),\n",
    "]\n",
    "\n",
    "trimmer.invoke(messages)\n"
   ],
   "id": "9fe3f208b5d312bd",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[SystemMessage(content='你是一个得力的助手，请使用{language}语言，用你出色的能力回答用户问题', additional_kwargs={}, response_metadata={}),\n",
       " HumanMessage(content='我喜欢吃冰淇淋', additional_kwargs={}, response_metadata={}),\n",
       " AIMessage(content='非常好', additional_kwargs={}, response_metadata={}),\n",
       " HumanMessage(content='2+2等于多少', additional_kwargs={}, response_metadata={}),\n",
       " AIMessage(content='4', additional_kwargs={}, response_metadata={}),\n",
       " HumanMessage(content='谢谢', additional_kwargs={}, response_metadata={}),\n",
       " AIMessage(content='没问题!', additional_kwargs={}, response_metadata={}),\n",
       " HumanMessage(content='玩的开心吗?', additional_kwargs={}, response_metadata={}),\n",
       " AIMessage(content='好的!', additional_kwargs={}, response_metadata={})]"
      ]
     },
     "execution_count": 23,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 23
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": [
    "要在我们的链中使用它，我们只需在将 messages 输入传递给提示之前运行修剪器。\n",
    "\n",
    "现在如果我们尝试询问模型我们的名字，它将不知道，因为我们修剪了聊天历史的那部分："
   ],
   "id": "87cf345ced67922c"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-09-18T01:43:53.640257Z",
     "start_time": "2025-09-18T01:43:53.637478Z"
    }
   },
   "cell_type": "code",
   "source": [
    "# <!--IMPORTS:[{\"imported\": \"RunnablePassthrough\", \"source\": \"langchain_core.runnables\", \"docs\": \"https://python.langchain.com/api_reference/core/runnables/langchain_core.runnables.passthrough.RunnablePassthrough.html\", \"title\": \"Build a Chatbot\"}]-->\n",
    "\n",
    "# https://www.langchain.com.cn/docs/tutorials/chatbot/\n",
    "from operator import itemgetter\n",
    "from langchain_core.runnables import RunnablePassthrough\n",
    "\n"
   ],
   "id": "8616a703827c71fd",
   "outputs": [],
   "execution_count": 24
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": [
    "# 流式处理\n",
    "\n",
    "现在我们有了一个功能齐全的聊天机器人。然而，对于聊天机器人应用程序来说，一个非常重要的用户体验考虑是流式处理。大型语言模型有时间可能需要一段时间才能响应，因此为了改善用户体验，大多数应用程序所做的一件事是随着每个令牌的生成流回。这样用户就可以看到进度。实际上，这非常简单！\n",
    "\n",
    "所有链都暴露一个 `.stream` 方法，使用消息历史的链也不例外。我们可以简单地使用该方法获取流式响应"
   ],
   "id": "7fd269e55b893466"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-09-18T01:45:07.016898Z",
     "start_time": "2025-09-18T01:44:57.829745Z"
    }
   },
   "cell_type": "code",
   "source": [
    "config = {\"configurable\": {\"session_id\": \"abc15\"}}\n",
    "for r in with_message_history.stream(\n",
    "        {\n",
    "            \"messages\": [HumanMessage(content=\"你好，我是小明, 给我讲一个笑话吧\")],\n",
    "            \"language\": \"中文\",\n",
    "        },\n",
    "        config=config,\n",
    "    ):\n",
    "        print(r.content, end=\"|\")"
   ],
   "id": "51c8dfe22431ff34",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "|当然|可以|，|小明|！|这里|有个|新|笑话|：\n",
      "\n",
      "|小明|去|餐厅|点|了一份|牛排|，|服务员|问|：“|先生|，|牛排|要|几分|熟|？”|  \n",
      "|小明|自信|地回答|：“|九|分|！”|  \n",
      "|服务员|愣了一下|：“|通常|我们|只|做到|全|熟|、|七|分|或|五分|哦|…”|  \n",
      "|小明|皱眉|：“|可|我想|吃|九|分|熟的|，|不行|吗|？”|  \n",
      "|服务员|无奈|：“|但|牛排|没有|九|分|熟|这个|选项|啊|…”|  \n",
      "|小明|突然|恍然大悟|：“|啊|！|那我|改成|‘|八|分|熟|加|一分|运气|’|吧|！”|  \n",
      "|服务员|：“|……|您|是不是|还想|点|一份|‘|百分|百|吃饱|’|的|套餐|？”|  \n",
      "\n",
      "|希望|这个|笑话|让你|笑|一笑|！| 😆||"
     ]
    }
   ],
   "execution_count": 26
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": [
    "# 下一步\n",
    "##### 现在你了解了如何在LangChain中创建聊天机器人的基础知识，你可能感兴趣的一些更高级的教程是：\n",
    "\n",
    "- [对话式RAG](https://www.langchain.com.cn/docs/tutorials/qa_chat_history/): 在外部数据源上启用聊天机器人体验\n",
    "- [代理](https://www.langchain.com.cn/docs/tutorials/agents/): 构建一个可以采取行动的聊天机器人\n",
    "##### 如果你想深入了解具体内容，值得查看的有：\n",
    "- [流式处理](https://www.langchain.com.cn/docs/how_to/streaming/): 流式处理对聊天应用程序是至关重要的\n",
    "- [如何添加消息历史](https://www.langchain.com.cn/docs/how_to/message_history/): 深入了解与消息历史相关的所有内容\n",
    "- [如何管理大量消息历史](https://www.langchain.com.cn/docs/how_to/trim_messages/): 管理大量聊天历史的更多技巧"
   ],
   "id": "23371bfd48dd2e5c"
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 2
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython2",
   "version": "2.7.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
